
package com.google.code.sms.controller;

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
import org.springframework.web.servlet.mvc.LastModified;

import com.google.code.sms.Logger;
import com.google.code.sms.domain.MediaElement;
import com.google.code.sms.service.MediaElementService;
import com.google.code.sms.service.SecurityService;
import com.google.code.sms.service.SettingsService;
import com.google.code.sms.util.StringUtil;

/**
 * Controller which produces cover art images.
 *
 * @author Sindre Mehus
 */
public class CoverArtController implements Controller, LastModified {

    public static final String ALBUM_COVERART_PREFIX = "al-";
    public static final String ARTIST_COVERART_PREFIX = "ar-";

    private static final Logger LOG = Logger.getLogger("CoverArtController");

    private SecurityService securityService;
    private MediaElementService mediaElementService;

    @Override
    public long getLastModified(HttpServletRequest request) {
        try {
            File file = getImageFile(request);
            if (file == null) {
                return 0;  // Request for the default image.
            }
            if (!file.exists()) {
                return -1;
            }

            return file.lastModified();
        } catch (Exception e) {
            return -1;
        }
    }

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        
        File file = getImageFile(request);

        if (file != null && !file.exists()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);
            return null;
        }

        // Check access.
        if (file != null && !securityService.isReadAllowed(file)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN);
            return null;
        }

        // Send default image if no path is given. (No need to cache it, since it will be cached in browser.)
        Integer size = ServletRequestUtils.getIntParameter(request, "size");
        if (file == null) {
            sendDefault(size, response);
            return null;
        }

        // Optimize if no scaling is required.
        if (size == null) {
            sendUnscaled(file, response);
            return null;
        }

        // Send cached image, creating it if necessary.
        try {
            File cachedImage = getCachedImage(file, size);
            sendImage(cachedImage, response);
        } catch (IOException e) {
            sendDefault(size, response);
        }

        return null;
    }

    private File getImageFile(HttpServletRequest request) {
        String id = request.getParameter("id");
        boolean getPoster = ServletRequestUtils.getBooleanParameter(request, "getPoster", false);
                
        if (id != null && !id.equals("")) 
        {
            if(getPoster)
                return getPoster(Integer.valueOf(id));
            else
                return getMediaElementImage(Integer.valueOf(id));
        }

        String path = StringUtils.trimToNull(request.getParameter("path"));
        return path != null ? new File(path) : null;
    }

    private File getMediaElementImage(int id) {
        MediaElement mediaElement = mediaElementService.getMediaElement(id);
        return mediaElement == null ? null : mediaElementService.getCoverArt(mediaElement);
    }
    
    private File getPoster(int id) {
        MediaElement mediaElement = mediaElementService.getMediaElement(id);
        return mediaElement == null ? null : mediaElementService.getPoster(mediaElement);
    }

    private void sendImage(File file, HttpServletResponse response) throws IOException {
        response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));
        InputStream in = new FileInputStream(file);
        try {
            IOUtils.copy(in, response.getOutputStream());
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    private void sendDefault(Integer size, HttpServletResponse response) throws IOException {
        response.setContentType(StringUtil.getMimeType("jpeg"));
        InputStream in = null;
        try {
            in = getClass().getResourceAsStream("default_cover.jpg");
            BufferedImage image = ImageIO.read(in);
            if (size != null) {
                image = scale(image, size, size);
            }
            ImageIO.write(image, "jpeg", response.getOutputStream());
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    private void sendUnscaled(File file, HttpServletResponse response) throws IOException {
        
        response.setContentType(StringUtil.getMimeType(FilenameUtils.getExtension(file.getName())));

        InputStream in = null;
        
        try {
            in = new FileInputStream(file);
            IOUtils.copy(in, response.getOutputStream());
        } finally {
            IOUtils.closeQuietly(in);
        }
    }

    private File getCachedImage(File file, int size) throws IOException {
        String md5 = DigestUtils.md5Hex(file.getPath());
        File cachedImage = new File(getImageCacheDirectory(size), md5 + ".jpeg");

        // Is cache missing or obsolete?
        if (!cachedImage.exists() || file.lastModified() > cachedImage.lastModified()) {
            InputStream in = null;
            OutputStream out = null;
            try {
                in = new FileInputStream(file);
                out = new FileOutputStream(cachedImage);
                BufferedImage image = ImageIO.read(in);
                if (image == null) {
                    throw new Exception("Unable to decode image.");
                }

                image = scale(image, size, size);
                ImageIO.write(image, "jpeg", out);

            } catch (Throwable x) {
                // Delete corrupt (probably empty) thumbnail cache.
                LOG.warn("Failed to create thumbnail for " + file, x);
                IOUtils.closeQuietly(out);
                cachedImage.delete();
                throw new IOException("Failed to create thumbnail for " + file + ". " + x.getMessage());

            } finally {
                IOUtils.closeQuietly(in);
                IOUtils.closeQuietly(out);
            }
        }
        return cachedImage;
    }

    private synchronized File getImageCacheDirectory(int size) {
        File dir = new File(SettingsService.getSMSHome(), "thumbs");
        dir = new File(dir, String.valueOf(size));
        if (!dir.exists()) {
            if (dir.mkdirs()) {
                LOG.info("Created thumbnail cache " + dir);
            } else {
                LOG.error("Failed to create thumbnail cache " + dir);
            }
        }

        return dir;
    }

    public static BufferedImage scale(BufferedImage image, int width, int height) {
        int w = image.getWidth();
        int h = image.getHeight();
        float multiplier;
        BufferedImage thumb = image;

        if (h > height)
        {
            multiplier = ((float)height / (float)h);
            h = height;
            w = (int)(w * multiplier);
        }
        
        if (w > width)
        {
            multiplier = ((float)width / (float)w);
            w = width;
            h = (int)(h * multiplier);
        }
        
        BufferedImage temp = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = temp.createGraphics();
        g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2.drawImage(thumb, 0, 0, temp.getWidth(), temp.getHeight(), null);
        g2.dispose();
        thumb = temp;

        return thumb;
    }

    public void setSecurityService(SecurityService securityService) {
        this.securityService = securityService;
    }

    public void setMediaElementService(MediaElementService mediaElementService) {
        this.mediaElementService = mediaElementService;
    }
}
